﻿/*
Copyright (c) 2009 Vratislav Cermak (vratacermak@gmail.com)
 
This file is part of TeachMe Project. See http://code.google.com/p/teach-me for more information.
 
TeachMe is an open source project. Using of TeachMe is free of charge.
It is distributed under GNU General Public License version 3 (GPLv3).
GPLv3 can be found at http://www.gnu.org/licenses/gpl.html.
 
This program is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE.  See the license for more details.
*/

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Windows.Forms;
using Aga.Controls.Tree;
using TeachMe.Application.Configuration;
using TeachMe.Application.Data;
using TeachMe.Application.Exceptions;
using TeachMe.Application.Properties;
using TeachMe.Application.Translators;

namespace TeachMe.Application.Forms
{
    using TeachMe.Application.ComboItems;

    public partial class AdvancedEntryEditorForm : PseudoModalForm
    {
        private class TranslateThreadContext
        {
            public ITranslationProvider TranslationProvider { get; set; }
            public string Word { get; set; }
            public string From { get; set; }
            public string To { get; set; }
        }

        private delegate void UpdateTranslationsToolbarDelegate();
        private delegate void AddTranslationsDelegate(Dictionary<string, string[]> translations);

        private readonly List<EditedEntryDescriptor> _newEntries = new List<EditedEntryDescriptor>();
        private readonly List<EditedEntryDescriptor> _entries;
        private readonly Root _root;
        private bool _ignoreTranslationsCheckStateChanged;
        private int _currentItemIdx;
        private SymbolsForm _symbolsForm;
        private TextBox _focusedEditor;
        private readonly TreeModel _modelTranslations;
        private bool _translating;
        private readonly object _translatingLock = new object();

        public AdvancedEntryEditorForm(Root root, List<EditedEntryDescriptor> entries, Dictionary defaultSelection)
        {
            _root = root;
            _entries = entries;
            _modelTranslations = new TreeModel();

            Dictionary = defaultSelection;

            _root.MachineSettings.CheckOut();
            _root.UserSettings.CheckOut();

            InitializeComponent();
            InitializeDictionaries(root, defaultSelection);

            _modelTranslations.NodesChanged += new EventHandler<TreeModelEventArgs>(OnModelNodesChanged);
            tvTranslations.Model = _modelTranslations;

            cbPriority.FillPriorities();

            cbTranslateAutomatically.Checked = _root.UserSettings.TranslateAutomatically.Value;

            if (_root.UserSettings.ShowSymbols.Value)
            {
                tsbDisplaySymbols.Checked = true;
                AdjustSymbolsForm();
            }

            WindowProportions.Lock(this, _root.MachineSettings);
        }

        private void InitializeDictionaries(Root root, Dictionary defaultSelection)
        {
            if (defaultSelection != null)
            {
                Dictionary = defaultSelection;

                ComboBoxItem<Dictionary> comboBoxItem = new ComboBoxItem<Dictionary>(Dictionary, Dictionary.Entries.Name);

                cbDictionary.Items.Add(comboBoxItem);
                cbDictionary.SelectedItem = comboBoxItem;
                cbDictionary.Enabled = false;
            }
            else
            {
                string lastEditedDictionary = _root.MachineSettings.LastEditedDictionaryPath;

                foreach (Dictionary dictionary in root.Dictionaries)
                {
                    ComboBoxItem<Dictionary> item = new ComboBoxItem<Dictionary>(dictionary, dictionary.Entries.Name);

                    cbDictionary.Items.Add(item);

                    if (String.Compare(dictionary.UserData.Path, lastEditedDictionary, true) == 0)
                    {
                        cbDictionary.SelectedItem = item;
                    }
                }
            }
        }

        private void UpdateView()
        {
            EditedEntryDescriptor descriptor = _entries[_currentItemIdx];

            tbFrom.Text = descriptor.From;
            tbTo.Text = descriptor.To;
            tbExample.Text = descriptor.Example;
            cbPriority.Priority = descriptor.Priority;

            _modelTranslations.Nodes.Clear();

            if (cbTranslateAutomatically.Checked && CanTranslate())
            {
                Translate();
            }
            else
            {
                UpdateTranslationsToolbar();
            }

            UpdateMainToolbar();
            ValidateFrom();
        }

        private bool CanTranslate()
        {
            ITranslationProvider[] translators = GetAvailableTranslators();
            return translators != null && translators.Length > 0;
        }

        private ITranslationProvider[] GetAvailableTranslators()
        {
            if (Dictionary == null)
            {
                return null;
            }

            string from = Dictionary.Entries.From;
            string to = Dictionary.Entries.To;

            if (String.IsNullOrEmpty(from) || String.IsNullOrEmpty(to))
            {
                return null;
            }

            Languages.Item fromLanguageItem = Singleton<Languages>.Instance.GetByCode(from);
            Languages.Item toLanguageItem = Singleton<Languages>.Instance.GetByCode(to);

            if (fromLanguageItem == null || toLanguageItem == null)
            {
                return null;
            }

            return Translators.Translators.GetAvailableTranslators(fromLanguageItem, toLanguageItem);
        }

        private void UpdateMainToolbar()
        {
            tsbFirst.Enabled = _currentItemIdx != 0;
            tsbLast.Enabled = _currentItemIdx != _entries.Count - 1;
            tsbPrevious.Enabled = _currentItemIdx > 0;
            tsbNext.Enabled = _currentItemIdx < _entries.Count - 1;
            tstProgress.Text = String.Format("({0}/{1})", _currentItemIdx + 1, _entries.Count);
        }

        private void UpdateCurrent()
        {
            EditedEntryDescriptor current = _entries[_currentItemIdx];

            current.To = tbTo.Text;
            current.From = tbFrom.Text;
            current.Example = tbExample.Text;
            current.Priority = cbPriority.Priority;
        }

        private void OnFormClosed(object sender, FormClosedEventArgs e)
        {
            if (_symbolsForm != null && _symbolsForm.Visible)
            {
                // closing symbols form so it can save it's size & position
                _symbolsForm.Close();
            }

            if (DialogResult == DialogResult.OK)
            {
                UpdateCurrent();

                if (Dictionary != null)
                {
                    _root.MachineSettings.LastEditedDictionaryPath = Dictionary.UserData.Path;
                }
                _root.UserSettings.TranslateAutomatically.Value = cbTranslateAutomatically.Checked;
            }

            _root.UserSettings.CheckIn();
            _root.MachineSettings.CheckIn();
        }

        private void OnFormClosing(object sender, FormClosingEventArgs e)
        {
            _root.UserSettings.ShowSymbols.Value = tsbDisplaySymbols.Checked;

            //

            if (DialogResult != DialogResult.OK || SkipSavingOnExit)
            {
                return;
            }

            try
            {
                UpdateCurrent();
                Dictionary.UpdateEntries(_entries);
            }
            catch (DictionaryException exception)
            {
                e.Cancel = true;
                Logging.Error(GetType(), exception, "Error updating entries.");
                MessageBox.Show(Resources.DictionarySaveError, Resources.MessageBoxCaption, MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }

        private void OnTranslateClick(object sender, EventArgs e)
        {
            Translate();
        }

        private void Translate()
        {
            string inputText = NormalizeFromText(tbFrom.Text);

            if (String.IsNullOrEmpty(inputText))
            {
                return;
            }

            TranslateThreadContext worker = new TranslateThreadContext { Word = inputText, From = Dictionary.Entries.From, To = Dictionary.Entries.To, TranslationProvider = (ITranslationProvider)tcbTranslators.SelectedItem };
            Thread thread = new Thread(TranslateThreadWorker) { CurrentUICulture = Thread.CurrentThread.CurrentUICulture };
            thread.Start(worker);
        }

        private static string NormalizeFromText(string from)
        {
            // removing pronunciation
            // http://code.google.com/p/teach-me/issues/detail?id=44

            if (!String.IsNullOrEmpty(from))
            {
                from = Regex.Replace(from, "\\[.*\\]", String.Empty);
            }

            if (!String.IsNullOrEmpty(from))
            {
                from = from.Trim();
            }

            return from;
        }

        private void AddTranslations(Dictionary<string, string[]> translations)
        {
            _modelTranslations.Nodes.Clear();

            try
            {
                _ignoreTranslationsCheckStateChanged = true;

                tvTranslations.BeginUpdate();

                foreach (KeyValuePair<string, string[]> pair in translations)
                {
                    Node rootNode = new Node(pair.Key);
                    rootNode.CheckState = CheckState.Unchecked;
                    _modelTranslations.Nodes.Add(rootNode);

                    foreach (string value in pair.Value)
                    {
                        Node valueNode = new Node(value);
                        valueNode.CheckState = CheckState.Unchecked;
                        rootNode.Nodes.Add(valueNode);
                    }
                }

                if (_modelTranslations.Nodes.Count > 0)
                {
                    tvTranslations.ExpandAll();
                }

                tvTranslations.EndUpdate();

                UpdateTranslatedTextsCheckState();
                UpdateParentsCheckState();
            }
            finally
            {
                _ignoreTranslationsCheckStateChanged = false;
            }
        }

        private IEnumerable<string> ExtractToValues()
        {
            string[] parts = Regex.Split(tbTo.Text, ",\\s*(?=[^)]*(?:\\(|$))");
            List<string> result = new List<string>();

            foreach (string part in parts)
            {
                string trimmed = part.Trim();
                if (!String.IsNullOrEmpty(trimmed))
                {
                    result.Add(trimmed);
                }
            }

            return result;
        }

        private void TranslateThreadWorker(object obj)
        {
            lock (_translatingLock)
            {
                _translating = true;
            }

            try
            {
                SafeUpdateTranslationsToolbar();

                TranslateThreadContext worker = obj as TranslateThreadContext;
                if (worker == null)
                {
                    throw new InvalidOperationException();
                }

                SafeAddTranslations(worker.TranslationProvider.Translate(worker.Word, worker.From, worker.To));
            }
            finally
            {
                lock (_translatingLock)
                {
                    _translating = false;
                }

                SafeUpdateTranslationsToolbar();
            }
        }

        private void SafeAddTranslations(Dictionary<string, string[]> translations)
        {
            if (InvokeRequired)
            {
                BeginInvoke(new AddTranslationsDelegate(AddTranslations), translations);
            }
            else
            {
                AddTranslations(translations);
            }
        }

        private void SafeUpdateTranslationsToolbar()
        {
            if (InvokeRequired)
            {
                BeginInvoke(new UpdateTranslationsToolbarDelegate(UpdateTranslationsToolbar));
            }
            else
            {
                UpdateTranslationsToolbar();
            }
        }

        private void OnSelectAllClick(object sender, EventArgs e)
        {
            SelectAll();
        }

        private void SelectAll()
        {
            foreach (Node node in GetTranslations())
            {
                node.CheckState = CheckState.Checked;
            }
        }

        private IEnumerable<Node> GetTranslations()
        {
            Queue<Node> nodes = new Queue<Node>(_modelTranslations.Nodes.Cast<Node>());

            while (nodes.Count > 0)
            {
                Node node = nodes.Dequeue();
                if (node.Nodes.Count <= 0)
                {
                    yield return node;
                }
                else
                {
                    foreach (Node child in node.Nodes)
                    {
                        nodes.Enqueue(child);
                    }
                }
            }
        }

        private void OnDeselectAllClicked(object sender, EventArgs e)
        {
            foreach (Node node in GetTranslations())
            {
                node.CheckState = CheckState.Unchecked;
            }
        }

        private void OnFormShown(object sender, EventArgs e)
        {
            UpdateView();
            UpdateButtons();
            UpdateTranslationsPanel();
            UpdateTranslationsToolbar();
            UpdateTexts();
        }

        private void UpdateTranslatedTextsCheckState()
        {
            IEnumerable<string> toValues = ExtractToValues();

            foreach (Node node in GetTranslations())
            {
                bool check = false;

                foreach (string toValue in toValues)
                {
                    if (String.Compare(toValue, node.Text, true) == 0)
                    {
                        check = true;
                        break;
                    }
                }

                node.CheckState = check ? CheckState.Checked : CheckState.Unchecked;
            }
        }

        void OnModelNodesChanged(object sender, TreeModelEventArgs e)
        {
            if (_ignoreTranslationsCheckStateChanged)
            {
                return;
            }

            List<string> modifiedValues = new List<string>();
            Node node = (Node)e.Children[0];
            bool newState = node.CheckState == CheckState.Checked;

            if (node.Nodes.Count == 0)
            {
                // simple node
                modifiedValues.Add(node.Text);
            }
            else
            {
                // root node, collect all children
                foreach (Node child in node.Nodes)
                {
                    modifiedValues.Add(child.Text);
                }
            }

            // parse current translation
            IEnumerable<string> toValues = ExtractToValues();

            StringBuilder sb = new StringBuilder();

            // update current translation (= remove items from current translation if not checked)
            foreach (string toValue in toValues)
            {
                bool found = modifiedValues.FirstOrDefault(x => String.Compare(toValue, x, true) == 0) != null;

                if (found)
                {
                    modifiedValues.Remove(toValue);
                }

                if (found && !newState)
                {
                    continue;
                }

                sb.Append(toValue);
                sb.Append(", ");
            }

            // add items from new selection
            if (newState)
            {
                foreach (string modifiedValue in modifiedValues)
                {
                    sb.Append(modifiedValue);
                    sb.Append(", ");
                }
            }

            tbTo.Text = sb.ToString().Trim(',', ' ');

            UpdateParentsCheckState();
            UpdateTranslationsToolbar();
        }

        private void UpdateParentsCheckState()
        {
            try
            {
                _ignoreTranslationsCheckStateChanged = true;

                foreach (Node node in _modelTranslations.Nodes)
                {
                    int checkedCount = node.Nodes.Count(x => x.CheckState == CheckState.Checked);

                    if (checkedCount > 0)
                    {
                        node.CheckState = checkedCount != node.Nodes.Count ? CheckState.Indeterminate : CheckState.Checked;
                    }
                    else
                    {
                        node.CheckState = CheckState.Unchecked;
                    }
                }
            }
            finally
            {
                _ignoreTranslationsCheckStateChanged = false;
            }
        }

        private void OnSelectedDictionaryChanged(object sender, EventArgs e)
        {
            ComboBoxItem<Dictionary> comboBoxItem = cbDictionary.SelectedItem as ComboBoxItem<Dictionary>;

            if (comboBoxItem != null)
            {
                Dictionary = comboBoxItem.Item;
            }

            UpdateButtons();
            UpdateTranslationsToolbar();
            UpdateTranslationsPanel();
            UpdateTexts();
            ValidateFrom();
        }

        private void UpdateTexts()
        {
            if (Dictionary == null)
            {
                txFrom.Text = Resources.EntryEditorFromNoLang;
                txTo.Text = Resources.EntryEditorToNoLang;

                return;
            }

            string from = Dictionary.Entries.From;
            string to = Dictionary.Entries.To;

            string fromLanguage = Singleton<Languages>.Instance.GetByCode(from) != null ? Singleton<Languages>.Instance.GetByCode(from).Localized : from;
            string toLanguage = Singleton<Languages>.Instance.GetByCode(to) != null ? Singleton<Languages>.Instance.GetByCode(to).Localized : to;

            txFrom.Text = String.Format(Resources.EntryEditorFrom, fromLanguage);
            txTo.Text = String.Format(Resources.EntryEditorTo, toLanguage);
        }

        private void UpdateButtons()
        {
            bOk.Enabled = Dictionary != null;
        }

        private void OnFirstClick(object sender, EventArgs e)
        {
            MoveToFirst();
        }

        private void MoveToFirst()
        {
            UpdateCurrent();

            _currentItemIdx = 0;

            UpdateView();
        }

        private void OnPreviousClick(object sender, EventArgs e)
        {
            MoveToPrevious();
        }

        private void MoveToPrevious()
        {
            UpdateCurrent();

            _currentItemIdx--;

            UpdateView();
        }

        private void OnNextClick(object sender, EventArgs e)
        {
            MoveToNext();
        }

        private void MoveToNext()
        {
            UpdateCurrent();

            _currentItemIdx++;

            UpdateView();
        }

        private void OnLastClick(object sender, EventArgs e)
        {
            MoveToLast();
        }

        private void MoveToLast()
        {
            UpdateCurrent();

            _currentItemIdx = _entries.Count - 1;

            UpdateView();
        }

        private void OnNewClick(object sender, EventArgs e)
        {
            AddNew();
        }

        private void AddNew()
        {
            UpdateCurrent();

            EditedEntryDescriptor entry = EditedEntryDescriptor.CreateNew();

            _newEntries.Add(entry);
            _entries.Add(entry);

            _currentItemIdx = _entries.Count - 1;

            UpdateView();

            // focus from edit when new entry is added
            // http://code.google.com/p/teach-me/issues/detail?id=43
            tbFrom.Focus();
        }

        private void UpdateTranslationsToolbar()
        {
            lock (_translatingLock)
            {
                tsbTranslate.Enabled = tbFrom.Text.Trim().Length > 0 && !_translating;
            }
            tsbSelectAll.Enabled = _modelTranslations.Nodes.Count > 0 && GetTranslations().Any(x => x.CheckState != CheckState.Checked);
            tsbDeselectAll.Enabled = _modelTranslations.Nodes.Count > 0 && GetTranslations().Any(x => x.CheckState == CheckState.Checked);
        }

        private void UpdateTranslationsPanel()
        {
            tcbTranslators.Items.Clear();

            bool enabled = true;

            ITranslationProvider[] providers = GetAvailableTranslators();

            if (providers == null || providers.Length == 0)
            {
                enabled = false;
            }

            tsTranslations.Enabled = enabled;
            tvTranslations.Enabled = enabled;

            if (!enabled)
            {
                return;
            }

            foreach (ITranslationProvider provider in providers)
            {
                tcbTranslators.Items.Add(provider);
            }

            tcbTranslators.SelectedIndex = 0;
        }

        private void OnToChanged(object sender, EventArgs e)
        {
            try
            {
                _ignoreTranslationsCheckStateChanged = true;

                UpdateTranslatedTextsCheckState();
                UpdateParentsCheckState();
            }
            finally
            {
                _ignoreTranslationsCheckStateChanged = false;
            }
        }

        private void OnFromChanged(object sender, EventArgs e)
        {
            UpdateTranslationsToolbar();
            ValidateFrom();
        }

        private void ValidateFrom()
        {
            errFromExists.Clear();

            if (!IsValid())
            {
                errFromExists.SetError(txFrom, String.Format(Resources.WordAlreadyUsed, tbFrom.Text.Trim()));
            }
        }

        private bool IsValid()
        {
            string trimmedFrom = tbFrom.Text.Trim();

            if (String.IsNullOrEmpty(trimmedFrom))
            {
                return true;
            }

            // search edited (displayed) entries
            foreach (var descriptor in _entries)
            {
                if (String.IsNullOrEmpty(descriptor.From) || descriptor == _entries[_currentItemIdx])
                {
                    continue;
                }

                if (String.Compare(tbFrom.Text.Trim(), descriptor.From.Trim(), true) == 0)
                {
                    return false;
                }
            }

            if (Dictionary != null)
            {
                // search all dictionary entries
                foreach (var entry in Dictionary.Entries.Items)
                {
                    // empty
                    if (String.IsNullOrEmpty(entry.Value.From))
                    {
                        continue;
                    }

                    // if entry is currently edited (_entries), it was already checked
                    var currentId = entry.Value.Id;
                    if (_entries.Any(descriptor => descriptor.State == EditedItemState.Normal && descriptor.Id == currentId))
                    {
                        continue;
                    }

                    // current one
                    if (_entries[_currentItemIdx] != null && _entries[_currentItemIdx].State == EditedItemState.Normal && _entries[_currentItemIdx].Id == entry.Value.Id)
                    {
                        continue;
                    }

                    if (String.Compare(tbFrom.Text.Trim(), entry.Value.From.Trim(), true) == 0)
                    {
                        return false;
                    }
                }
            }

            return true;
        }

        protected override bool ProcessCmdKey(ref Message msg, Keys keyData)
        {
            if (Utilities.Is(keyData, Keys.Control | Keys.N))
            {
                if (tsbNew.Enabled)
                {
                    AddNew();
                }
                return true;
            }

            if (Utilities.Is(keyData, Keys.Control | Keys.Left))
            {
                if (tsbPrevious.Enabled)
                {
                    MoveToPrevious();
                }
                return true;
            }

            if (Utilities.Is(keyData, Keys.Control | Keys.Right))
            {
                if (tsbNext.Enabled)
                {
                    MoveToNext();
                }
                return true;
            }

            if (Utilities.Is(keyData, Keys.Control | Keys.Home))
            {
                if (tsbFirst.Enabled)
                {
                    MoveToFirst();
                }
                return true;
            }

            if (Utilities.Is(keyData, Keys.Control | Keys.End))
            {
                if (tsbLast.Enabled)
                {
                    MoveToLast();
                }
                return true;
            }

            if (Utilities.Is(keyData, Keys.Control | Keys.T))
            {
                if (tsbTranslate.Enabled)
                {
                    Translate();
                }
                return true;
            }

            if (Utilities.Is(keyData, Keys.A | Keys.Control) && tvTranslations.Focused)
            {
                SelectAll();
            }

            return base.ProcessCmdKey(ref msg, keyData);
        }

        public bool NewEntriesAvailable
        {
            get
            {
                return _newEntries.Count > 0;
            }
        }

        public Dictionary Dictionary { get; private set; }

        public List<EditedEntryDescriptor> GetNewEntries()
        {
            return _newEntries;
        }

        private void OnDisplaySymbolsClick(object sender, EventArgs e)
        {
            AdjustSymbolsForm();
        }

        private void AdjustSymbolsForm()
        {
            if (tsbDisplaySymbols.Checked)
            {
                if (_symbolsForm == null || _symbolsForm.IsDisposed)
                {
                    _symbolsForm = new SymbolsForm(_root);
                    _symbolsForm.Closed += delegate
                                               {
                                                   tsbDisplaySymbols.Checked = false;
                                               };
                    _symbolsForm.SymbolSelected += OnSymbolsFormSymbolSelected;
                }

                if (!_symbolsForm.Visible)
                {
                    _symbolsForm.Show(this);
                }
            }
            else
            {
                _symbolsForm.Hide();
            }
        }

        void OnSymbolsFormSymbolSelected(object sender, Events.SymbolEventArgs e)
        {
            if (_focusedEditor != null)
            {
                _focusedEditor.SelectedText = e.Symbol.ToString();
            }
        }

        private void OnEditorEnter(object sender, EventArgs e)
        {
            _focusedEditor = sender as TextBox;
        }

        private void OnEditorLeave(object sender, EventArgs e)
        {
            _focusedEditor = null;
        }

        public bool SkipSavingOnExit { get; set; }
    }
}
